fix: avoid dev-hook false positives in non-dev commands#356
fix: avoid dev-hook false positives in non-dev commands#356tsubasakong wants to merge 3 commits intoaffaan-m:mainfrom
Conversation
📝 WalkthroughWalkthroughToken-level parsing is added to the dev-server-block hook: it extracts the leading command word of each shell segment (skipping recognized prefixes) and only applies the dev-pattern/tmux checks when that leading word is a known package-manager command, preventing false positives from heredoc or prose content. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/hooks/pre-bash-dev-server-block.js">
<violation number="1" location="scripts/hooks/pre-bash-dev-server-block.js:111">
P2: Wrapper option tokens are treated as the command word, which lets `env`/`sudo`-prefixed dev commands bypass blocking.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/hooks/pre-bash-dev-server-block.js`:
- Around line 20-21: The parsing currently lets env -S/--split-string bypass
because the split-string argument (the following token) is ignored; update the
parser so PREFIX_OPTION_VALUE_WORDS handling treats '-S' and '--split-string' as
options that consume the next token as the command string and skips that
consumed token when computing the leading command; modify getLeadingCommandWord
to recognize and advance past the split-string value (same change for the other
occurrences referenced around lines 128-133) so env -S "npm run dev" yields
"npm" as the leading command.
- Around line 13-17: When a wrapper command from DEV_COMMAND_WORDS is detected,
we should not run devPattern against the entire raw segment because quoted
arguments (e.g., bash -lc 'echo "npm run dev"' or tmux display-message "npm run
dev") cause false positives; instead, extract the inner command string passed to
the wrapper (strip wrapper name and common flags like -c, -lc, --, and
tmux/display-message argument wrappers) or skip quoted/heredoc sections, then
run devPattern only on that extracted/unquoted command; update the logic that
currently calls devPattern on the full raw segment so it first detects wrapper
commands in DEV_COMMAND_WORDS, extracts the actual child command, unquotes or
removes heredoc content, and only then applies devPattern to avoid
wrapper-induced false positives.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d7146ffb-9e1e-4191-b02e-e608fa2114cd
📒 Files selected for processing (2)
scripts/hooks/pre-bash-dev-server-block.jstests/integration/hooks.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/integration/hooks.test.js
| 'bash', | ||
| 'sh', | ||
| 'zsh', | ||
| 'fish', | ||
| 'tmux' |
There was a problem hiding this comment.
Wrapper commands can still false-positive on quoted text.
Because bash, sh, zsh, fish, and tmux are in DEV_COMMAND_WORDS, Line 165 still runs devPattern on the full raw segment after the wrapper is identified. Cases like bash -lc 'echo "npm run dev"' or tmux display-message "npm run dev" will still be blocked even though they never start a dev server, so the quoted/heredoc false-positive path remains for wrapper commands.
Also applies to: 160-166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/hooks/pre-bash-dev-server-block.js` around lines 13 - 17, When a
wrapper command from DEV_COMMAND_WORDS is detected, we should not run devPattern
against the entire raw segment because quoted arguments (e.g., bash -lc 'echo
"npm run dev"' or tmux display-message "npm run dev") cause false positives;
instead, extract the inner command string passed to the wrapper (strip wrapper
name and common flags like -c, -lc, --, and tmux/display-message argument
wrappers) or skip quoted/heredoc sections, then run devPattern only on that
extracted/unquoted command; update the logic that currently calls devPattern on
the full raw segment so it first detects wrapper commands in DEV_COMMAND_WORDS,
extracts the actual child command, unquotes or removes heredoc content, and only
then applies devPattern to avoid wrapper-induced false positives.
| const PREFIX_OPTION_VALUE_WORDS = { | ||
| env: new Set(['-u', '-C', '-S', '--unset', '--chdir', '--split-string']), |
There was a problem hiding this comment.
env -S currently creates a bypass.
-S / --split-string hold the command string that env will execute, but this path skips that value entirely. env -S "npm run dev" therefore returns null from getLeadingCommandWord() and never reaches the blocker, which is the opposite of the wrapper support this PR is adding.
💡 Minimal direction
if (activeWrapper && isOptionToken(token)) {
if (shouldSkipOptionValue(activeWrapper, token)) {
+ if (activeWrapper === 'env' && (token === '-S' || token === '--split-string')) {
+ const value = readToken(segment, index);
+ return value ? getLeadingCommandWord(value.token) : null;
+ }
skipNextValue = true;
}
continue;
}Also applies to: 128-133
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/hooks/pre-bash-dev-server-block.js` around lines 20 - 21, The parsing
currently lets env -S/--split-string bypass because the split-string argument
(the following token) is ignored; update the parser so PREFIX_OPTION_VALUE_WORDS
handling treats '-S' and '--split-string' as options that consume the next token
as the command string and skips that consumed token when computing the leading
command; modify getLeadingCommandWord to recognize and advance past the
split-string value (same change for the other occurrences referenced around
lines 128-133) so env -S "npm run dev" yields "npm" as the leading command.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
|
Opened #371 to carry the currently-validated maintainer path for this fix set. It includes equivalent coverage here, folds in related portability/docs fixes, and has already passed local \
Validated 16 agent files ━━━ Running lib/utils.test.js ━━━ === Testing utils.js === Platform Detection: Directory Functions: Date/Time Functions: Project Name Functions: Session ID Functions: File Operations: findFiles: Edge Cases: System Functions: output() and log(): isGitRepo(): getGitModifiedFiles(): getLearnedSkillsDir(): replaceInFile (behavior): writeFile (edge cases): findFiles (regex chars): readStdinJson(): grepFile (global regex fix): commandExists Edge Cases: findFiles Edge Cases: ensureDir Edge Cases: runCommand Edge Cases: getGitModifiedFiles Edge Cases: replaceInFile Edge Cases: readStdinJson Edge Cases: readStdinJson maxSize truncation: ensureDir Error Propagation (Round 31): runCommand failure output (Round 31): runCommand Security (allowlist + metacharacters): getGitModifiedFiles empty patterns (Round 31): readStdinJson error event (Round 33): getGitModifiedFiles all-invalid patterns (Round 69): Round 71: findFiles (unreadable subdirectory in recursive scan): Round 79: countInFile (valid string pattern): Round 79: grepFile (valid string pattern): Round 84: findFiles (inner statSync catch — broken symlink): getSessionIdShort fallback (Round 85): Round 88: replaceInFile with empty replacement string (deletion): Round 88: countInFile with existing file but non-matching pattern: Round 92: countInFile (non-string non-RegExp pattern): Round 93: countInFile (case-insensitive RegExp, g flag auto-appended): Round 93: countInFile (case-insensitive RegExp, g flag preserved): Round 95: countInFile (regex alternation without g flag): Round 97: getSessionIdShort (whitespace-only session ID): Round 97: countInFile (RegExp lastIndex reuse validation): Round 98: findFiles (maxAge: -1 — negative boundary excludes all): Round 99: replaceInFile (no-match still returns true): Round 99: grepFile (CR-only line endings — classic Mac format): Round 100: findFiles (maxAge + recursive combined — untested interaction): Round 101: output() (circular reference — JSON.stringify crash): Round 103: countInFile (boolean false — explicit type guard returns 0): Round 103: grepFile (numeric 0 — implicit toString via RegExp constructor): Round 105: grepFile (sticky y flag — not stripped like g, stateful .test() bug): Round 107: grepFile (empty line matching — ^$ on split lines, trailing \n creates extra empty element): Round 107: replaceInFile (replacement contains search pattern — String.replace is single-pass): Round 106: countInFile (named capture groups — String.match(g) returns full matches only): Round 106: grepFile (multiline m flag — preserved in regex, unlike g which is stripped): Round 109: appendFile (new file creation — ensureDir creates parent, appendFileSync creates file): Round 108: grepFile (Unicode/emoji — regex matching on UTF-16 split lines): Round 110: findFiles (root directory unreadable — EACCES on readdirSync caught silently): Round 113: replaceInFile (zero-width regex /(?:)/g — matches every position): Round 114: replaceInFile (options.all silently ignored for RegExp search): Round 114: output (object containing BigInt — JSON.stringify throws): Round 115: countInFile (empty string pattern — matches at every zero-width position): Round 117: grepFile (CRLF content — trailing \r breaks anchored regex patterns): Round 116: replaceInFile (null/undefined replacement — JS coerces to string "null"/"undefined"): Round 116: ensureDir (null path — fs.existsSync(null) throws TypeError): Round 118: writeFile (non-string content — TypeError propagates uncaught): Round 119: appendFile (non-string content — TypeError propagates like writeFile): Round 120: replaceInFile (empty string search — replace vs replaceAll dramatic difference): Round 121: findFiles (? glob pattern — converted to . regex for single char match): Round 122: findFiles (dot escaping — *.txt matches file.txt but not filetxt): Round 123: countInFile (overlapping patterns — String.match(/g/) is non-overlapping): Round 123: replaceInFile ( Round 124: findFiles (* glob matches dotfiles — unlike shell globbing): Round 125: readFile (binary/non-UTF8 content — garbled, not null): Round 125: output() (undefined/NaN/Infinity — typeof checks and JSON.stringify): === Test Results === [Utils] replaceInFile failed for /var/folders/ck/y983js7n5lz1z5ls6fq8v0cm0000gn/T/utils-test-readonly-1773115897985/readonly.txt: EACCES: permission denied, open '/var/folders/ck/y983js7n5lz1z5ls6fq8v0cm0000gn/T/utils-test-readonly-1773115897985/readonly.txt' ━━━ Running lib/package-manager.test.js ━━━ === Testing package-manager.js === PACKAGE_MANAGERS Constant: detectFromLockFile: detectFromPackageJson: getAvailablePackageManagers: getPackageManager: getRunCommand: getExecCommand: getCommandPattern: getSelectionPrompt: setProjectPackageManager: setPreferredPackageManager: detectFromPackageJson (edge cases): getExecCommand (edge cases): getRunCommand (additional): DETECTION_PRIORITY: getCommandPattern (additional): getPackageManager (robustness): getRunCommand (validation): getExecCommand (validation): getPackageManager (source detection): setPreferredPackageManager (success): getCommandPattern (completeness): getRunCommand (PM-specific formats): getExecCommand (PM-specific formats): getExecCommand (args validation): getCommandPattern (regex escaping): getRunCommand (non-string input): getExecCommand (non-string binary): getCommandPattern (escapeRegex completeness): getPackageManager (global config edge cases): Round 30: getCommandPattern edge cases: setProjectPackageManager (write verification, Round 31): getExecCommand (safe argument edge cases, Round 31): Round 34: getExecCommand non-string args: Round 34: detectFromPackageJson with non-string packageManager: Round 48: detectFromPackageJson (version format edge cases): Round 69: getPackageManager (global-config success): Round 71: setPreferredPackageManager (save failure): Round 72: setProjectPackageManager (save failure): Round 80: getExecCommand (truthy non-string args): Round 86: detectFromPackageJson (empty package.json): Round 91: getCommandPattern (empty action): Round 91: detectFromPackageJson (whitespace-only packageManager): Round 92: detectFromPackageJson (empty string packageManager): Round 94: detectFromPackageJson (scoped package name @scope/pkg@version): Round 94: getPackageManager (empty string CLAUDE_PACKAGE_MANAGER env var): Round 104: detectFromLockFile (null projectDir — throws TypeError): Round 105: getExecCommand (object args — typeof bypass coerces to [object Object]): Round 109: getExecCommand (path traversal in binary — SAFE_NAME_REGEX permits ../ in binary name): Round 108: getRunCommand (path traversal — SAFE_NAME_REGEX permits ../ via allowed / and . chars): Round 111: getExecCommand (newline in args — SAFE_ARGS_REGEX \s matches \n): === Test Results === ━━━ Running lib/session-manager.test.js ━━━ === Testing session-manager.js === parseSessionFilename: parseSessionMetadata: getSessionStats: Session CRUD: getSessionSize: getSessionTitle: getAllSessions: getSessionById: parseSessionMetadata (edge cases): getSessionStats (edge cases): getSessionSize (edge cases): parseSessionFilename (additional edge cases): writeSessionContent: appendSessionContent: deleteSession: sessionExists: getAllSessions (pagination edge cases): getSessionStats (code blocks & special chars): parseSessionFilename (30-day month validation): getSessionStats (path heuristic edge cases): getAllSessions (combined filters): getSessionById (ambiguous prefix): parseSessionMetadata (edge cases): Round 43: getSessionById (default excludes content): Round 54: search filter scope and path utility: Round 66: getSessionById (noIdMatch — date-only match for old format): Round 30: datetime local-time fix: Round 30: parseSessionFilename edge cases: createdTime fallback (Round 33): getSessionStats Windows path heuristic (Round 46): parseSessionMetadata checkbox case sensitivity (Round 46): Round 69: getSessionById (missing sessions directory): Round 78: getSessionStats (actual file path → reads from disk): Round 78: getAllSessions (hasContent field): Round 75: deleteSession (unlink failure in read-only dir): Round 81: getSessionStats(null) (null input): Round 83: getAllSessions (broken symlink — statSync catch): Round 84: getSessionById (broken symlink — statSync catch): Round 88: parseSessionMetadata content lacking Date/Started/Updated fields: Round 89: getAllSessions (subdirectory skip): Round 91: getSessionStats (mixed Windows path separators): Round 92: getSessionStats (Windows UNC path): Round 93: getSessionStats (drive letter without slash — regex boundary): Round 95: getAllSessions (both negative offset and negative limit): Round 96: parseSessionFilename (Feb 30 — impossible date): Round 96: getAllSessions (limit: Infinity — pagination bypass): Round 96: getAllSessions (limit: null — destructuring default bypass): Round 97: getAllSessions (whitespace search — truthy but unmatched): Round 98: getSessionById (null sessionId — crashes at line 297): Round 98: parseSessionFilename (null input — crashes at line 30): Round 99: writeSessionContent (null path — error handling): Round 100: parseSessionMetadata (### in item text — lazy regex truncation): Round 101: getSessionStats (non-string input — type confusion crash): Round 101: appendSessionContent (null path — error handling): Round 102: getSessionStats (Unix nonexistent .tmp path — looksLikePath → null content): Round 102: parseSessionMetadata ([x] items in In Progress — regex skips checked): Round 104: parseSessionMetadata (whitespace-only notes — trim reduces to empty): Round 105: parseSessionMetadata (blank line inside section — regex stops at \n\n): Round 106: getAllSessions (array/object limit coercion — Number([5])→5, Number({})→NaN→50): Round 109: getAllSessions (non-session .tmp files — parseSessionFilename returns null → skip): Round 108: getSessionSize (exact 1024-byte boundary — < means 1024 is KB, 1023 is B): Round 110: parseSessionFilename (year 0000 — Date constructor maps 0→1900): Round 110: parseSessionFilename (uppercase ID — regex [a-z0-9]{8,} rejects [A-Z]): Round 111: parseSessionMetadata (nested Round 112: getSessionStats (newline-in-path heuristic): Round 112: appendSessionContent (read-only file): Round 113: parseSessionFilename (century leap year — 100/400 rules): Round 113: parseSessionMetadata (title with markdown formatting — raw markdown preserved): Round 115: parseSessionMetadata (CRLF line endings — \r\n vs \n in section regexes): Round 117: getSessionSize (B/KB/MB formatting at exact boundary thresholds): Round 117: parseSessionFilename (uppercase short ID — regex [a-z0-9] rejects uppercase): Round 119: parseSessionMetadata ("Context to Load" — code block extraction edge cases): Round 120: parseSessionMetadata ("Notes for Next Session" — extraction edge cases): Round 121: parseSessionMetadata (Started/Last Updated time extraction): Round 122: getSessionById (old format no-id — date-only filename match): Round 123: parseSessionMetadata (CRLF section boundaries — \n\n fails to match \r\n\r\n): Round 124: getAllSessions (invalid date format — strict !== comparison): Round 124: parseSessionMetadata (title regex edge cases — /^#\s+(.+)$/m): Results: Passed: 165, Failed: 0 [SessionManager] Error writing session: ENOENT: no such file or directory, open '/nonexistent/deep/path/session.tmp' ━━━ Running lib/session-aliases.test.js ━━━ === Testing session-aliases.js === loadAliases: setAlias: resolveAlias: listAliases: deleteAlias: renameAlias: updateAliasTitle: resolveSessionAlias: getAliasesForSession: cleanupAliases: listAliases (edge cases): setAlias (edge cases): updateAliasTitle (edge cases): saveAliases (atomic write): cleanupAliases (edge cases): renameAlias (edge cases): getAliasesForSession (edge cases): setAlias (reserved names case sensitivity): listAliases (negative limit): setAlias (undefined title): saveAliases (failure paths, Round 31): renameAlias rollback (Round 33): saveAliases backup/restore (Round 33): Round 39: atomic overwrite: Round 48: rapid sequential saves: Round 56: Windows platform atomic write path: Round 64: loadAliases version/metadata backfill: Round 67: loadAliases (empty 0-byte file): Round 67: resolveSessionAlias (null/falsy input): Round 67: loadAliases (metadata-only backfill, version present): updateAliasTitle save failure (Round 70): Round 72: deleteAlias (save failure): Round 73: cleanupAliases (save failure): Round 73: setAlias (save failure): Round 84: listAliases (NaN date fallback in sort comparator): Round 86: loadAliases (truthy non-object aliases field): Round 90: saveAliases (backup restore double failure): Round 95: renameAlias (self-rename same name): Round 100: cleanupAliases (callback returns 0 — falsy non-boolean coercion): Round 102: setAlias (title=0 — falsy coercion silently converts to null): Round 103: loadAliases (array aliases — typeof bypass): Round 104: resolveSessionAlias (path-traversal input — returned unchanged): Round 107: setAlias (whitespace-only title — truthy string stored as-is, unlike sessionPath which is trim-checked): Round 111: setAlias (128-char alias — exact boundary of > 128 check): Round 112: resolveAlias (Unicode rejection): Round 114: listAliases (non-string search — number triggers TypeError): Round 115: updateAliasTitle (empty string title — stored null, returned ""): Round 116: loadAliases (extra unknown JSON fields — preserved by loose validation): Round 118: renameAlias (same name — "already exists" because data.aliases[newAlias] is truthy): Round 118: setAlias (reserved names — case-insensitive rejection): Round 119: renameAlias (reserved newAlias name — parallel check to setAlias): Round 120: setAlias (max alias length boundary — 128 ok, 129 rejected): Round 121: setAlias (sessionPath validation — null, empty, whitespace, non-string): Round 122: listAliases (limit edge cases — 0/negative/NaN are falsy, return all): Round 125: loadAliases (proto key in JSON — safe, no prototype pollution): Results: Passed: 105, Failed: 0 [Aliases] Error parsing aliases file: Unexpected token N in JSON at position 0 ━━━ Running lib/project-detect.test.js ━━━ === Testing project-detect.js === Rule Definitions: Empty Directory: Python Detection: TypeScript/JavaScript Detection: Go Detection: Rust Detection: Ruby Detection: PHP Detection: Fullstack Detection: Dependency Readers: Elixir Detection: Edge Cases: === Results: 28 passed, 0 failed === ━━━ Running hooks/hooks.test.js ━━━ === Testing Hook Scripts === session-start.js: session-start.js (edge cases): check-console-log.js: session-end.js: pre-compact.js: suggest-compact.js: evaluate-session.js: post-edit-console-warn.js: post-edit-format.js: pre-bash-dev-server-block.js: post-edit-typecheck.js: session-end.js (extractSessionSummary): hooks.json Validation: plugin.json Validation: evaluate-session.js: suggest-compact.js: check-console-log.js (exact pass-through): post-edit-format.js (security & extension tests): post-edit-typecheck.js (security & extension tests): Shell wrapper portability: Round 23: evaluate-session.js (config & nullish coalescing): Round 23: session-end.js (update existing file path): Round 23: pre-compact.js (glob specificity): Round 23: session-end.js (extractSessionSummary edge cases): Round 24: suggest-compact.js (interval fix & fd fallback): Round 24: post-edit-format.js (edge cases): Round 24: post-edit-typecheck.js (edge cases): Round 24: session-start.js (edge cases): Round 25: post-edit-console-warn.js (pass-through fix): Round 25: check-console-log.js (edge cases): Round 29: post-edit-format.js (cwd and exit): Round 29: post-edit-typecheck.js (exit and pass-through): Round 29: post-edit-console-warn.js (extension and exit): Round 29: check-console-log.js (exclusion patterns and exit): Round 29: run-all.js test runner improvements: Round 32: post-edit-typecheck (special character paths): Round 32: check-console-log (edge cases): Round 32: post-edit-console-warn (additional edge cases): Round 32: session-end.js (empty transcript): Round 38: evaluate-session.js (tilde expansion & missing config): Round 41: pre-compact.js (multiple session files): Round 40: session-end.js (newline collapse): Round 44: session-start.js (empty session file): Round 49: post-edit-typecheck.js (extension edge cases): Round 49: session-end.js (conditional summary sections): Round 50: session-start.js (alias reporting): Round 50: pre-compact.js (parallel execution): Round 50: session-start.js (graceful degradation): Round 53: post-edit-console-warn.js (max matches truncation): Round 53: post-edit-format.js (non-existent file): Round 55: session-start.js (maxAge 7-day boundary): Round 55: session-start.js (newest session selection): Round 55: session-end.js (stdin overflow): Round 56: post-edit-typecheck.js (tsconfig in parent directory): Round 56: suggest-compact.js (counter file as directory — fallback path): Round 59: session-start.js (unreadable session file — readFile returns null): Round 59: check-console-log.js (stdin exceeding 1MB — truncation): Round 59: pre-compact.js (read-only session file — appendFile error): Round 60: session-end.js (replaceInFile returns false — timestamp update warning): Round 60: post-edit-console-warn.js (stdin exceeding 1MB — truncation): Round 60: post-edit-format.js (valid JSON without tool_input key): Round 64: post-edit-typecheck.js (valid JSON without tool_input): Round 66: session-end.js (entry.role user fallback): Round 66: session-end.js (nonexistent transcript path): Round 70: session-end.js (entry.name/entry.input fallback): Round 71: session-start.js (default source — selection prompt): Round 74: session-start.js (main catch — unrecoverable error): Round 75: pre-compact.js (main catch — unrecoverable error): Round 75: session-end.js (main catch — unrecoverable error): Round 76: evaluate-session.js (main catch — unrecoverable error): Round 76: suggest-compact.js (main catch — double-failure): Round 80: session-end.js (entry.message.role user — third OR condition): Round 81: suggest-compact.js (COMPACT_THRESHOLD > 10000): Round 81: session-end.js (user entry with non-string non-array content): Round 82: session-end.js (entry.tool_name without type=tool_use): Round 82: session-end.js (template marker present but regex no-match): Round 87: post-edit-format.js (stdin exceeding 1MB — truncation): Round 87: post-edit-typecheck.js (stdin exceeding 1MB — truncation): Round 89: post-edit-typecheck.js (TypeScript error detection path): Round 89: session-end.js (entry.name + entry.input fallback in extractSessionSummary): Round 90: readStdinJson (timeout fires when stdin stays open): Round 94: session-end.js (tools used without files modified): === Test Results === ━━━ Running hooks/evaluate-session.test.js ━━━ === Testing evaluate-session.js === Threshold boundary (default min=10): Edge cases: Config file parsing: Round 53: CLAUDE_TRANSCRIPT_PATH fallback: Round 65: regex whitespace tolerance around colon: Round 85: config parse error catch block: Round 86: config learned_skills_path override: Results: Passed: 16, Failed: 0 ━━━ Running hooks/suggest-compact.test.js ━━━ === Testing suggest-compact.js === Basic counter functionality: Threshold suggestion: Interval suggestion: Environment variable handling: Corrupted counter file: Session isolation: Exit code: Threshold boundary values: Default session ID fallback (Round 64): Results: Passed: 20, Failed: 0 ━━━ Running integration/hooks.test.js ━━━ === Hook Integration Tests === Hook Input Format Handling: Hook Output Format: Hook Exit Codes: Realistic Scenarios: Session End Transcript Parsing: Error Handling: Round 51: Timeout Enforcement: Round 51: hooks.json Schema Validation: === Test Results === ━━━ Running ci/validators.test.js ━━━ === Testing CI Validators === validate-agents.js: validate-hooks.js: validate-skills.js: validate-commands.js: validate-rules.js: validate-hooks.js (whitespace edge cases): validate-agents.js (whitespace edge cases): validate-commands.js (additional edge cases): validate-hooks.js (schema edge cases): validate-hooks.js (legacy format errors): validate-agents.js (empty directory): validate-commands.js (whitespace edge cases): validate-rules.js (mixed files): validate-hooks.js (Round 27 edge cases): validate-commands.js (Round 27 edge cases): validate-skills.js (mixed dirs): Round 30: validate-commands (skill warnings): Round 30: validate-agents (model validation): Round 32: validate-agents (empty frontmatter): Round 32: validate-rules (non-file entries): Round 32: validate-commands (agent reference with valid workflow): Round 42: validate-agents (case sensitivity): Round 42: validate-commands (missing agents dir): Round 42: validate-hooks (empty matchers array): Round 47: validate-hooks (inline JS escape sequences): Round 47: validate-agents (frontmatter lines without colon): Round 52: validate-commands (inline backtick refs): Round 52: validate-commands (workflow whitespace): Round 52: validate-rules (code-only content): Round 57: validate-skills.js (SKILL.md is a directory — readFileSync error): Round 57: validate-rules.js (broken symlink — statSync catch block): Round 57: validate-commands.js (adjacent code blocks both stripped): Round 58: validate-agents.js (unreadable agent file — readFileSync catch): Round 58: validate-agents.js (frontmatter line with colon at position 0): Round 58: validate-hooks.js (command is a plain object — not string or array): Round 63: validate-hooks.js (object-format matcher missing matcher field): Round 63: validate-commands.js (unreadable command file): Round 63: validate-commands.js (empty commands directory): Round 65: validate-rules.js (empty directory — no .md files): Round 65: validate-skills.js (empty directory — no subdirectories): Round 70: validate-commands.js (would create: skip): Round 72: validate-hooks.js (async and timeout type validation): Round 73: validate-commands.js (unreadable skill entry — statSync catch): Round 76: validate-hooks.js (invalid JSON in hooks.json): Round 78: validate-hooks.js (wrapped hooks format): Round 79: validate-commands.js (warnings count in output): Round 80: validate-hooks.js (legacy array format): Round 82: validate-hooks (Notification and SubagentStop event types): Round 83: validate-agents (whitespace-only frontmatter field value): Round 83: validate-skills (empty SKILL.md file): Results: Passed: 136, Failed: 0 ━━━ Running scripts/claw.test.js ━━━ === Testing claw.js === Storage: Context: Delegation: REPL/Meta: NanoClaw v2: Results: Passed: 19, Failed: 0 ━━━ Running scripts/setup-package-manager.test.js ━━━ === Testing setup-package-manager.js === --help: --detect: --list: --global: --project: positional argument: environment variable: --detect output completeness: --global flag validation (Round 31): --project flag validation (Round 31): --detect marker uniqueness (Round 45): --list output completeness (Round 45): --global success path (Round 62): bare PM name success (Round 62): --detect source label (Round 62): --project success path (Round 68): --list (current) marker (Round 68): Round 74: setGlobal catch (save failure): Round 74: setProject catch (save failure): Results: Passed: 31, Failed: 0 ━━━ Running scripts/skill-create-output.test.js ━━━ === Testing skill-create-output.js === SkillCreateOutput constructor: header(): analysisResults(): patterns(): instincts(): output(): nextSteps(): footer(): progressBar edge cases: empty array edge cases: box() crash prevention: box() alignment: box() content overflow: header() width alignment (Round 34): box() width accuracy (Round 35): analysisResults zero values (Round 54): demo export (Round 68): Round 85: patterns() confidence=0 nullish coalescing: Round 87: analyzePhase() async method: Results: Passed: 36, Failed: 0 ╔══════════════════════════════════════════════════════════╗ |
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
affaan-m
left a comment
There was a problem hiding this comment.
Automated review: this PR has merge conflicts. Please rebase or resolve.
Description
env npm run devwhile allowing commands likegh pr createthat mention dev commands inside quoted or heredoc textFixes #352
Validation:
Type of Change
fix:Bug fixfeat:New featurerefactor:Code refactoringdocs:Documentationtest:Testschore:Maintenance/toolingci:CI/CD changesChecklist
node tests/run-all.js)